package com.example.controller;

import cn.hutool.core.io.FileUtil;
import com.example.service.FileDetailService;
import com.obs.services.ObsClient;
import com.obs.services.exception.ObsException;
import com.obs.services.model.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.dromara.x.file.storage.core.FileInfo;
import org.dromara.x.file.storage.core.FileStorageService;
import org.dromara.x.file.storage.core.ProgressListener;
import org.dromara.x.file.storage.core.platform.HuaweiObsFileStorage;
import org.dromara.x.file.storage.core.platform.MultipartUploadSupportInfo;
import org.dromara.x.file.storage.core.platform.UpyunUssFileStorage;
import org.dromara.x.file.storage.core.recorder.FileRecorder;
import org.dromara.x.file.storage.core.upload.FilePartInfo;
import org.dromara.x.file.storage.core.upload.FilePartInfoList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@RestController
@Api(tags = "文件模块")
public class FileDetailController {

    @Autowired
    private FileStorageService fileStorageService;//注入实列

    @Autowired
    private FileDetailService fileDetailService;

    public static void main(String[] args) {

        // 您的工程中可以只保留一个全局的ObsClient实例
// ObsClient是线程安全的，可在并发场景下使用
        ObsClient obsClient = null;
        try {
            String endPoint = "http://obs.cn-east-3.myhuaweicloud.com";
            String ak = "EWGYXHBAMGH2GMUTGFFZ";
            String sk = "477LboFMzhzHPvPMN4p4B5fDopUhdCON0WsC49QB";
            // 创建ObsClient实例
            obsClient = new ObsClient(ak, sk, endPoint);
            // 调用接口进行操作，例如上传对象
            PutObjectResult response = obsClient.putObject("dev-test-obs", "测试.xlsx", new File("C:\\Users\\user\\Downloads\\原始库存数据.xlsx"));  // localfile为待上传的本地文件路径，需要指定到具体的文件名
            System.out.println(response);
        } catch (ObsException e) {
            System.out.println("HTTP Code: " + e.getResponseCode());
            System.out.println("Error Code:" + e.getErrorCode());
            System.out.println("Error Message: " + e.getErrorMessage());

            System.out.println("Request ID:" + e.getErrorRequestId());
            System.out.println("Host ID:" + e.getErrorHostId());
        } finally {
            // 关闭ObsClient实例，如果是全局ObsClient实例，可以不在每个方法调用完成后关闭
            // ObsClient在调用ObsClient.close方法关闭后不能再次使用
            if (obsClient != null) {
                // obsClient.close();
            }
        }

//        try {
//            URL url = new URL("https://dev-test-obs.obs.cn-east-3.myhuaweicloud.com/web/%E6%B5%8B%E8%AF%95.xlsx");
//            URLConnection connection = url.openConnection();
//            InputStream is = connection.getInputStream();
//            BufferedReader br = new BufferedReader(new InputStreamReader(is, "gb2312"));
//            System.out.println(br);
//        } catch (MalformedURLException e) {
//            e.printStackTrace();
//        } catch (IOException e) {
//            e.printStackTrace();
//        }

    }

    /**
     * 上传文件
     */
    @PostMapping("/upload")
    @ApiOperation("上传文件")
    public FileInfo upload(MultipartFile file) {
        return fileStorageService.of(file).upload();
    }

    /**
     * 上传文件，成功返回文件 url
     */
    @PostMapping("/upload2")
    @ApiOperation("上传文件，成功返回文件 url")
    public String upload2(MultipartFile file) {
        FileInfo fileInfo = fileStorageService.of(file)
                .setPath("upload/") //保存到相对路径下，为了方便管理，不需要可以不写
                .setObjectId("0")   //关联对象id，为了方便管理，不需要可以不写
                .setObjectType("0") //关联对象类型，为了方便管理，不需要可以不写
                .putAttr("role", "admin") //保存一些属性，可以在切面、保存上传记录、自定义存储平台等地方获取使用，不需要可以不写
                .upload();  //将文件上传到对应地方
        return fileInfo == null ? "上传失败！" : fileInfo.getUrl();
    }

    @PostMapping("/upload3")
    @ApiOperation("上传文件，成功返回文件 url并保存")
    public String upload3(MultipartFile file) {

        FileInfo fileInfo = fileStorageService.of(file)
                .setPath("upload/") //保存到相对路径下，为了方便管理，不需要可以不写
                .setObjectId("0")   //关联对象id，为了方便管理，不需要可以不写
                .setObjectType("0") //关联对象类型，为了方便管理，不需要可以不写
                .putAttr("role", "admin") //保存一些属性，可以在切面、保存上传记录、自定义存储平台等地方获取使用，不需要可以不写
                .upload();  //将文件上传到对应地方
        String url = fileInfo.getUrl();
//        fileDetailService.save(fileInfo);
        return fileInfo == null ? "上传失败！" : fileInfo.getUrl();
    }

    /**
     * 上传图片，成功返回文件信息
     * 图片处理使用的是 https://github.com/coobird/thumbnailator
     */
    @PostMapping("/upload-image")
    @ApiOperation("上传图片，成功返回文件信息")
    public FileInfo uploadImage(MultipartFile file) {
        return fileStorageService.of(file)
                .image(img -> img.size(1000, 1000))  //将图片大小调整到 1000*1000
                .thumbnail(th -> th.size(200, 200))  //再生成一张 200*200 的缩略图
                .upload();
    }

    /**
     * 上传文件到指定存储平台，成功返回文件信息
     */
    @PostMapping("/upload-platform")
    @ApiOperation("上传文件到指定存储平台，成功返回文件信息")
    public FileInfo uploadPlatform(MultipartFile file) {
        return fileStorageService.of(file)
                .setPlatform("huawei-obs-1")    //使用指定的存储平台
                .upload();
    }

    /**
     * 直接读取 HttpServletRequest 中的文件进行上传，成功返回文件信息
     * 使用这种方式有些注意事项，请查看文档 基础功能-上传 章节
     */
    @PostMapping("/upload-request")
    @ApiOperation("直接读取 HttpServletRequest")
    public FileInfo uploadPlatform(HttpServletRequest request) {
        return fileStorageService.of(request).upload();
    }

    @PostMapping("/exist")
    @ApiOperation("查询文件是否存在")
    public boolean exist(String fileName) {
        //手动构造文件信息，可用于其它操作
        FileInfo fileInfo = new FileInfo()
                .setPlatform("huawei-obs-1")
                .setBasePath("test/")
//                .setPath("upload/")
                .setFilename("6674daabf61e8d25ba3e144e.jpg")
                .setThFilename("6674daabf61e8d25ba3e144e.jpg.min.jpg");

//文件是否存在
        boolean exists = fileStorageService.exists(fileInfo);
//下载
        byte[] bytes = fileStorageService.download(fileInfo).bytes();
//删除
        boolean delete = fileStorageService.delete(fileInfo);
        return exists;
    }

    @PostMapping("/exist1")
    @ApiOperation("数据库中获取 FileInfo 对象")
    public FileInfo exist1(String fileName) {
//直接从数据库中获取 FileInfo 对象，更加方便执行其它操作


        FileInfo fileInfo = fileStorageService.getFileInfoByUrl(fileName);

//文件是否存在
        boolean exists = fileStorageService.exists(fileName);
//下载
        byte[] bytes = fileStorageService.download(fileName).bytes();
//删除
        fileStorageService.delete(fileName);
//其它更多操作

        return fileInfo;
    }


    @PostMapping("/support")
    @ApiOperation("是否支持分片上传")
    public MultipartUploadSupportInfo support() {
        //当前默认的存储平台支付支持手动分片上传
        MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload();
        supportInfo.getIsSupport();//是否支持手动分片上传，正常情况下判断此参数就行了
        supportInfo.getIsSupportListParts();//是否支持列举已上传的分片
        supportInfo.getIsSupportAbort();//是否支持取消上传

        return supportInfo;
    }

    @PostMapping("/putMetadata")
    @ApiOperation("手动分片上传-初始化")
    public FileInfo putMetadata(MultipartFile file) throws IOException {
        long l = System.currentTimeMillis();
        //是否为华为云 obs
//        boolean isUpyunUss = fileStorageService.getFileStorage() instanceof HuaweiObsFileStorage;
        //手动分片上传-初始化
        FileInfo fileInfo = fileStorageService.initiateMultipartUpload()
                .setPath("test/")   // 保存到相对路径下，为了方便管理，不需要可以不写
                .setSaveFilename("jdk-11.0.20_windows-x64_bin1.exe")
//                .putMetadata(isUpyunUss, "X-Upyun-Multi-Part-Size", String.valueOf(10 * 1024 * 1024))// 设置 Metadata，不需要可以不写
                .init();
                byte[] AllBytes = FileUtil.readBytes("D:\\google download\\jdk-11.0.20_windows-x64_bin.exe");//分片数据，和基本的上传方式一样，也支持各种数据源
// 每段上传100MB
        long partSize = 20 * 1024 * 1024L;
        long fileSize = AllBytes.length;
        // 计算需要上传的段数
        long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;
        final List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<PartEtag>());
        List<byte[]> split = splitBytesIntoChunks(AllBytes, 20);
        // 初始化线程池
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i = 0; i < partCount; i++) {
            // 分段在文件中的起始位置
            final long offset = i * partSize;
            byte[] bytes = split.get(i);
            // 分段大小
            final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
            // 分段号
            final int partNumber = i + 1;
            executorService.execute(
                    new Runnable() {
                        @Override
                        public void run() {
                            FilePartInfo filePartInfo = fileStorageService.uploadPart(fileInfo, partNumber, bytes, (long) bytes.length).upload();

                        }
                    });
        }
        // 等待上传完成
        executorService.shutdown();
        while (!executorService.isTerminated()) {
            try {
                executorService.awaitTermination(5, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        fileStorageService.completeMultipartUpload(fileInfo).complete();
        long l1 = System.currentTimeMillis();
        System.out.println("putMetadata花费时间"+(l1-l));
        System.out.println(fileInfo);
        return fileInfo;
    }
    public List<byte[]> splitBytesIntoChunks(byte[] bytes, int chunkSizeMB) {
        // 将MB转换为字节，1MB = 1024 * 1024 字节
        int chunkSize = chunkSizeMB * 1024 * 1024;

        // 计算总共有多少个20MB大小的块，以及最后一块的大小
        int totalChunks = (int) Math.ceil((double) bytes.length / chunkSize);
        List<byte[]> chunks = new ArrayList<>(totalChunks);

        for (int i = 0; i < totalChunks; i++) {
            // 计算当前块的开始和结束索引
            int start = i * chunkSize;
            int end = Math.min(start + chunkSize, bytes.length);

            // 创建并添加当前块到列表中
            byte[] chunk = Arrays.copyOfRange(bytes, start, end);
            chunks.add(chunk);
        }

        return chunks;
    }
    @PostMapping("/putMetadata1")
    @ApiOperation("手动分片上传-默认版本")
    public FileInfo putMetadata1(MultipartFile file) throws IOException {
        long l = System.currentTimeMillis();
        byte[] bytes = file.getBytes();

        FileInfo fileInfo = fileStorageService.of(file).upload();
        long l1 = System.currentTimeMillis();

        System.out.println("putMetadata1花费时间"+(l1-l));

        return fileInfo;
    }


    @PostMapping("/segmentedUpload")
    @ApiOperation("分段上传原生demo")
    public  void segmentedUpload() {
        // 您可以通过环境变量获取访问密钥AK/SK，也可以使用其他外部引入方式传入。如果使用硬编码可能会存在泄露风险。
        // 您可以登录访问管理控制台获取访问密钥AK/SK
        String endPoint = "http://obs.cn-east-3.myhuaweicloud.com";
        String ak = "EWGYXHBAMGH2GMUTGFFZ";
        String sk = "477LboFMzhzHPvPMN4p4B5fDopUhdCON0WsC49QB";
        // 【可选】如果使用临时AK/SK和SecurityToken访问OBS，同样建议您尽量避免使用硬编码，以降低信息泄露风险。
        // 您可以通过环境变量获取访问密钥AK/SK/SecurityToken，也可以使用其他外部引入方式传入。
        String securityToken = System.getenv("SECURITY_TOKEN");
        // endpoint填写桶所在的endpoint, 此处以华北-北京四为例，其他地区请按实际情况填写。
        // 您可以通过环境变量获取endPoint，也可以使用其他外部引入方式传入。
        //String endPoint = System.getenv("ENDPOINT");

        // 创建ObsClient实例
        // 使用永久AK/SK初始化客户端
        ObsClient obsClient = new ObsClient(ak, sk,endPoint);
        // 使用临时AK/SK和SecurityToken初始化客户端
        // ObsClient obsClient = new ObsClient(ak, sk, securityToken, endPoint);

        try {
            final String bucketName = "dev-test-obs";
            final String objectKey = "jdk-11.0.20_windows-x64_bin.exe";
            // 初始化线程池
            ExecutorService executorService = Executors.newFixedThreadPool(20);
            final File largeFile = new File("D:\\google download\\jdk-11.0.20_windows-x64_bin.exe");
            // 初始化分段上传任务
            InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, objectKey);
            InitiateMultipartUploadResult result = obsClient.initiateMultipartUpload(request);
            final String uploadId = result.getUploadId();
            System.out.println("\t" + uploadId + "\n");
            // 每段上传100MB
            long partSize = 20 * 1024 * 1024L;
            long fileSize = largeFile.length();
            // 计算需要上传的段数
            long partCount = fileSize % partSize == 0 ? fileSize / partSize : fileSize / partSize + 1;
            final List<PartEtag> partEtags = Collections.synchronizedList(new ArrayList<PartEtag>());
            // 执行并发上传段
            for (int i = 0; i < partCount; i++) {
                // 分段在文件中的起始位置
                final long offset = i * partSize;
                // 分段大小
                final long currPartSize = (i + 1 == partCount) ? fileSize - offset : partSize;
                // 分段号
                final int partNumber = i + 1;
                executorService.execute(
                        new Runnable() {
                            @Override
                            public void run() {
                                UploadPartRequest uploadPartRequest = new UploadPartRequest();
                                uploadPartRequest.setBucketName(bucketName);
                                uploadPartRequest.setObjectKey(objectKey);
                                uploadPartRequest.setUploadId(uploadId);
                                uploadPartRequest.setFile(largeFile);
                                uploadPartRequest.setPartSize(currPartSize);
                                uploadPartRequest.setOffset(offset);
                                uploadPartRequest.setPartNumber(partNumber);
                                UploadPartResult uploadPartResult;
                                try {
                                    uploadPartResult = obsClient.uploadPart(uploadPartRequest);
                                    System.out.println("Part#" + partNumber + " done\n");
                                    partEtags.add(
                                            new PartEtag(uploadPartResult.getEtag(), uploadPartResult.getPartNumber()));
                                } catch (ObsException e) {
                                    e.printStackTrace();
                                }
                            }
                        });
            }
            // 等待上传完成
            executorService.shutdown();
            while (!executorService.isTerminated()) {
                try {
                    executorService.awaitTermination(5, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 合并段
            CompleteMultipartUploadRequest completeMultipartUploadRequest =
                    new CompleteMultipartUploadRequest(bucketName, objectKey, uploadId, partEtags);
            obsClient.completeMultipartUpload(completeMultipartUploadRequest);
            System.out.println("completeMultipartUpload successfully");
        } catch (ObsException e) {
            System.out.println("CompleteMultipartUpload failed");
            // 请求失败,打印http状态码
            System.out.println("HTTP Code:" + e.getResponseCode());
            // 请求失败,打印服务端错误码
            System.out.println("Error Code:" + e.getErrorCode());
            // 请求失败,打印详细错误信息
            System.out.println("Error Message:" + e.getErrorMessage());
            // 请求失败,打印请求id
            System.out.println("Request ID:" + e.getErrorRequestId());
            System.out.println("Host ID:" + e.getErrorHostId());
            e.printStackTrace();
        } catch (Exception e) {
            System.out.println("completeMultipartUpload failed");
            // 其他异常信息打印
            e.printStackTrace();
        }
    }


    @PostMapping("/monitorUploadProgress")
    @ApiOperation("监听上传进度")
    public String monitorUploadProgress(MultipartFile file) throws IOException {
        // 方式一
        fileStorageService.of(file).setProgressListener(progressSize ->
                System.out.println("已上传：" + progressSize)
        ).upload();

// 方式二
        fileStorageService.of(file).setProgressListener((progressSize,allSize) ->
                System.out.println("已上传 " + progressSize + " 总大小" + (allSize == null ? "未知" : allSize))
        ).upload();

// 方式三
        fileStorageService.of(file).setProgressListener(new ProgressListener() {
            @Override
            public void start() {
                System.out.println("上传开始");
            }

            @Override
            public void progress(long progressSize,Long allSize) {
                System.out.println("已上传 " + progressSize + " 总大小" + (allSize == null ? "未知" : allSize));
            }

            @Override
            public void finish() {
                System.out.println("上传结束");
            }
        }).upload();


        return "success";
    }


    @PostMapping("/Download")
    @ApiOperation("下载")
    public FileInfo Download(MultipartFile file) throws IOException {
        // 获取文件信息
        FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://dev-test-obs.obs.cn-east-3.myhuaweicloud.com/test/test/微信图片_20240624101946.jpg");

        // 下载为字节数组
        byte[] bytes = fileStorageService.download(fileInfo).bytes();

// 下载到文件
        fileStorageService.download(fileInfo).file("D:\\a.jpg");

        // 下载到 OutputStream 中
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        fileStorageService.download(fileInfo).outputStream(out);

// 获取 InputStream 手动处理
        fileStorageService.download(fileInfo).inputStream(in -> {
            //TODO 读取 InputStream
        });

// 直接通过文件信息中的 url 下载，省去手动查询文件信息记录的过程
        fileStorageService.download("https://dev-test-obs.obs.cn-east-3.myhuaweicloud.com/test/test/微信图片_20240624101946.jpg").file("D:\\a.jpg");

// 下载缩略图
        fileStorageService.downloadTh(fileInfo).file("D:\\th.jpg");

        return fileInfo;
    }

    @PostMapping("/downloadProgress")
    @ApiOperation("监听下载进度")
    public FileInfo downloadProgress(MultipartFile file) throws IOException {
        // 获取文件信息
        FileInfo fileInfo = fileStorageService.getFileInfoByUrl("https://dev-test-obs.obs.cn-east-3.myhuaweicloud.com/test/test/微信图片_20240624101946.jpg");
// 方式一
        fileStorageService.download(fileInfo).setProgressListener(progressSize ->
                System.out.println("已下载：" + progressSize)
        ).file("D:\\a.jpg");

// 方式二
        fileStorageService.download(fileInfo).setProgressListener((progressSize,allSize) ->
                System.out.println("已下载 " + progressSize + " 总大小" + allSize)
        ).file("D:\\b.jpg");

// 方式三
        fileStorageService.download(fileInfo).setProgressListener(new ProgressListener() {
            @Override
            public void start() {
                System.out.println("下载开始");
            }

            @Override
            public void progress(long progressSize,Long allSize) {
                System.out.println("已下载 " + progressSize + " 总大小" + allSize);
            }

            @Override
            public void finish() {
                System.out.println("下载结束");
            }
        }).file("D:\\c.jpg");

        return fileInfo;
    }




}
